﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;


namespace PentaBiz.Interop
{
    /// <summary>
    ///     Marshalling.
    ///     Managed to Unmanged conversion extensions.
    ///     (used for managedObject.GetUnamangedByteArray() and unamangedByteArray.ToManagedObject() :) )
    ///     <para>
    ///         Used resources:
    ///         http://www.developerfusion.com/article/84519/mastering-structs-in-c/
    ///         http://forums.codeguru.com/showthread.php?394360-Copying-Single-dimension-Array-to-Multi-Dimension-array
    ///     </para>
    /// </summary>
    public static class MarshalExtensions
    {
        #region decimals

        public static byte[] MarshalToByteArray(this decimal[] decimals)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(decimals);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = decimals.Length*Marshal.SizeOf(typeof (decimal));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this decimal[,] decimals)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(decimals);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = decimals.Length*Marshal.SizeOf(typeof (decimal));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this decimal[,,] decimals)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(decimals);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = decimals.Length*Marshal.SizeOf(typeof (decimal));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static decimal[] ReadOneDimensiondecimalArray(this byte[] buffer, uint arrayLength)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var decimals = new decimal[arrayLength];

                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return decimals;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static decimal[,] ReadTwoDimensiondecimalArray(this byte[] buffer, uint length1, uint length2)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var decimals = new decimal[length1,length2];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return decimals;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static decimal[,,] ReadThreeDimensiondecimalArray(this byte[] buffer, uint length1, uint length2,
                                                                 uint length3)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var decimals = new decimal[length1,length2,length3];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(decimals, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return decimals;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        #endregion

        #region doubles

        public static byte[] MarshalToByteArray(this double[] doubles)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(doubles);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = doubles.Length*Marshal.SizeOf(typeof (double));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this double[,] doubles)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(doubles);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = doubles.Length*Marshal.SizeOf(typeof (double));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this double[,,] doubles)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(doubles);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = doubles.Length*Marshal.SizeOf(typeof (double));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static double[] ReadOneDimensionDoubleArray(this byte[] buffer, uint arrayLength)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var doubles = new double[arrayLength];

                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return doubles;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static double[,] ReadTwoDimensionDoubleArray(this byte[] buffer, uint length1, uint length2)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var doubles = new double[length1,length2];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return doubles;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static double[,,] ReadThreeDimensionDoubleArray(this byte[] buffer, uint length1, uint length2,
                                                               uint length3)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var doubles = new double[length1,length2,length3];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return doubles;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        #endregion

        #region floats

        public static byte[] MarshalToByteArray(this float[] floats)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(floats);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = floats.Length*Marshal.SizeOf(typeof (float));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this float[,] floats)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(floats);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = floats.Length*Marshal.SizeOf(typeof (float));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static byte[] MarshalToByteArray(this float[,,] floats)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(floats);

            try
            {
                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);

                //Use array.Length * Marshal.SizeOf(elementType)
                int rawsize = floats.Length*Marshal.SizeOf(typeof (float));
                var rawdata = new byte[rawsize];


                //Use the marshal class to do a direct copy of the elements. NO LOOPING
                Marshal.Copy(tPtr, rawdata, 0, rawsize);

                return rawdata;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static float[] ReadOneDimensionfloatArray(this byte[] buffer, uint arrayLength)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var floats = new float[arrayLength];

                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return floats;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static float[,] ReadTwoDimensionfloatArray(this byte[] buffer, uint length1, uint length2)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var floats = new float[length1,length2];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return floats;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        public static float[,,] ReadThreeDimensionfloatArray(this byte[] buffer, uint length1, uint length2,
                                                             uint length3)
        {
            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gchSourceArray = GCHandle.Alloc(buffer);

            try
            {
                var floats = new float[length1,length2,length3];


                //Create a IntPtr variable to hold the returned pointer to the index in the array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(floats, 0);


                Marshal.Copy(buffer, 0, tPtr, buffer.Length);


                return floats;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gchSourceArray.Free();
            }
        }

        #endregion

        /// <summary>
        ///     Marshals to byte array.
        ///     <para>
        ///         The struct must marked with <see cref="LayoutKind" />.<see cref="LayoutKind.Explicit" /> or
        ///         <see
        ///             cref="LayoutKind" />
        ///         .<see cref="LayoutKind.Sequential" /> with Packed=1
        ///     </para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="struct">The item.</param>
        /// <returns></returns>
        public static byte[] MarshalToByteArray<T>(this T @struct) where T : struct
        {
            int rawsize = Marshal.SizeOf(typeof (T));
            var rawdata = new byte[rawsize];
            GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);

            Marshal.StructureToPtr(@struct, handle.AddrOfPinnedObject(), false);
            handle.Free();
            return rawdata;
        }


        /// <summary>
        ///     Reads the struct from the stream.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <returns></returns>
        public static T ReadStruct<T>(this Stream stream) where T : struct
        {
            int rawsize = Marshal.SizeOf(typeof (T));

            var buffer = new byte[rawsize];
            stream.Read(buffer, 0, rawsize);
            GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

            var @struct = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (T));
            handle.Free();


            return @struct;
        }

        /// <summary>
        ///     Writes the struct.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="struct">The item.</param>
        /// <returns></returns>
        public static void WriteStruct<T>(this Stream stream, T @struct) where T : struct
        {
            int rawsize = Marshal.SizeOf(typeof (T));
            var rawdata = new byte[rawsize];
            GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);

            Marshal.StructureToPtr(@struct, handle.AddrOfPinnedObject(), false);
            handle.Free();

            stream.Write(rawdata, 0, rawdata.Length);

            //return stream;
        }

        /// <summary>
        ///     Marshals the byte array to managed struct.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="bytes">The bytes.</param>
        /// <returns>
        ///     Managed <typeparamref name="T">struct</typeparamref>
        /// </returns>
        public static T MarshalToStruct<T>(this byte[] bytes) where T : struct
        {
            //ValidateStructAsSequential<T>();


            GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

            var temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (T));
            handle.Free();
            return temp;
        }

        /// <summary>
        ///     Marshals the managed struct to unmanaged pointer.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="struct">The struct.</param>
        /// <returns>Unmanaged pointer</returns>
        public static IntPtr MarshalToPointer<T>(this T @struct) where T : struct
        {
            IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf(@struct));
            Marshal.StructureToPtr(@struct, buf, false);
            return buf;
        }

        /// <summary>
        ///     Marshals the unmanaged pointer to struct.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="intPtr">The int PTR.</param>
        /// <returns>Managed struct</returns>
        public static T MarshalToStruct<T>(this IntPtr intPtr) where T : struct
        {
            return (T) Marshal.PtrToStructure(intPtr, typeof (T));
        }

        /// <summary>
        ///     Marshals the single dimension of a Two dimension array.
        /// </summary>
        /// <param name="doubles">The array.</param>
        /// <param name="dimensionIndex">Index of the dimension.</param>
        /// <returns>The actual dimansion array</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">dimensionIndex;dimensionIndex must be between 0 and array.GetLength(0)</exception>
        public static double[] MarshalSingleDimension(this double[,] doubles, int dimensionIndex)
        {
            if (dimensionIndex < 0 || dimensionIndex >= doubles.GetLength(0))
                throw new ArgumentOutOfRangeException("dimensionIndex", dimensionIndex,
                                                      "dimensionIndex must be between 0 and array.GetLength(0)");

            var arrayLength1 = doubles.GetUpperBound(1);


            //Create a garbage collection handle
            //What this does is prevent the GC from shuffling around your array data while you are copying the elements
            //Set it to the array, this 'pins' the array in place
            GCHandle gcHandle = GCHandle.Alloc(doubles);

            try
            {
                var index = dimensionIndex + dimensionIndex*arrayLength1;

                //Create a IntPtr variable to hold the returned pointer to the index in the array
                //Get a pointer to the pinned adress of the multi dimensional array,
                //treat the multi array as a flat array
                var tPtr = Marshal.UnsafeAddrOfPinnedArrayElement(doubles, index);

                var xArray = new double[arrayLength1 + 1];

                //Marshal Copy the array directly
                Marshal.Copy(tPtr, xArray, 0, xArray.Length);

                return xArray;
            }
            finally
            {
                //ALWAYS free the handle, this is very important.
                gcHandle.Free();
            }
        }
    }
}